<?php
require_once "protected/include/ks3File/Ks3Client.class.php";
require_once "protected/include/ks3File/core/Utils.class.php";
require_once 'vendor/autoload.php';
require_once "protected/include/AliOSS/autoload.php";
use OSS\OssClient;
use OSS\Core\OssException;
/**
 * 系统开放接口
 * @author licx
 * @date 2025-07-10
 */
class MediaController extends Controller {
    function init(){
        parent::init(); // 调用父类init方法
        header("Content-type: application/json; charset=utf-8");
        require(APP_DIR.'/protected/include/functions.php');
        
        // 执行全局鉴权
        $this->auth();
    }
    
    /**
     * 接口签名鉴权
     */
    private function auth() {
        // 获取请求参数（支持GET和POST）
        $params = $_GET;
        if (!empty($_POST)) {
            $params = array_merge($params, $_POST);
        }
        
        // 验证必要参数
        $required = ['channel_id', 'timestamp', 'nonce', 'signature'];
        foreach ($required as $field) {
            if (!isset($params[$field]) || empty($params[$field])) {
                $this->outputError(400, "缺少必要参数：{$field}");
            }
        }
        
        // 验证时间戳有效期（5分钟内）
        $now = time();
        if (abs($now - $params['timestamp']) > 300) {
            $this->outputError(401, "请求已过期，请重新发起");
        }
        
        // 验证nonce唯一性（防止重放攻击，可根据实际情况用Redis存储）
        if (!$this->checkNonce($params['nonce'], $params['channel_id'])) {
            $this->outputError(401, "无效的随机数");
        }
        
        // 查询渠道信息
        $channel = $this->getChannelInfo($params['channel_id']);
        if (!$channel) {
            $this->outputError(401, "渠道不存在或已禁用");
        }
        
        // 生成签名并验证
        $sign = $this->generateSign($params, $channel['app_secret']);
        if ($sign !== $params['signature']) {
            $this->outputError(401, "签名验证失败");
        }
    }
    
    /**
     * 获取渠道信息
     * @param string $channelId
     * @return array|bool
     */
    private function getChannelInfo($channelId) {
        // 这里假设使用PDO连接数据库，实际请根据项目数据库配置修改
        $db = new PDO("mysql:host=localhost;dbname=your_db;charset=utf8mb4", "username", "password");
        $stmt = $db->prepare("SELECT * FROM api_channels WHERE channel_id = :channel_id AND status = 1 LIMIT 1");
        $stmt->execute([':channel_id' => $channelId]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }
    
    /**
     * 验证nonce唯一性
     * @param string $nonce
     * @param string $channelId
     * @return bool
     */
    private function checkNonce($nonce, $channelId) {
        // 实际项目中建议用Redis存储已使用的nonce，设置5分钟过期时间
        // 这里仅为示例，实际需实现存储逻辑
        $redis = new ZDRedis();
        $key = "api_nonce:{$channelId}:{$nonce}";
        if ($redis->isExists($key)) {
            return false;
        }
        $redis->set($key, "nonce", 300); // 5分钟过期
        return true;
    }
    
    /**
     * 生成签名
     * @param array $params
     * @param string $appSecret
     * @return string
     */
    private function generateSign($params, $appSecret) {
        // 去除签名参数
        unset($params['signature']);
        // 按字典序排序
        ksort($params);
        // 拼接字符串
        $string = '';
        foreach ($params as $k => $v) {
            $string .= "{$k}={$v}&";
        }
        // 拼接密钥
        $string .= "app_secret={$appSecret}";
        // 生成MD5签名（实际项目可根据需求使用sha256等算法）
        return md5($string);
    }
    /**
     * 新增媒体内容
     * POST /api/CreateMediaAction
     */
    public function actionCreateMediaAction() {
        $params = $this->getParams();
        
        // 验证必填字段
        $required = [
            'content_type', 'content_url', 'title', 
            'category', 'submit_user_id', 'submit_time', 'priority',
            'ai_preview_result', 'audit_status'
        ];
        foreach ($required as $field) {
            if (empty($params[$field])) {
                $this->outputError(400, "缺少必要参数：{$field}");
            }
        }

        // 验证枚举值范围
        $this->validateEnums($params);        

        // 处理日期格式
        $dateFields = ['submit_time', 'pub_time', 'audit_time', 'claimed_time'];
        foreach ($dateFields as $field) {
            if (!empty($params[$field]) && !strtotime($params[$field])) {
                $this->outputError(400, "{$field}格式错误，需为datetime格式");
            }
        }

        // 处理JSON字段
        $jsonFields = ['illegal_details', 'extra'];
        foreach ($jsonFields as $field) {
            if (!empty($params[$field]) && !$this->isValidJson($params[$field])) {
                $this->outputError(400, "{$field}格式错误，需为JSON字符串");
            }
        }

        // 插入数据
        $columns = [];
        $values = [];
        foreach ($params as $k => $v) {
            if (in_array($k, $this->getTableColumns())) {
                $columns[] = "`{$k}`";
                $values[":{$k}"] = $v;
            }
        }
        // 生成content_id 业务ID（如"MED-8721"，加长长度适配更多场景）
        $content_id = 'MED-' . date('YmdHis') . mt_rand(1000, 9999);
        $media = new MediaContent();
        $media->create([
            'content_id' => $content_id,
            'content_type' => $params['content_type'],
            'content_url' => $params['content_url'],
            'title' => $params['title'],
            'category' => $params['category'],
            'submit_user_id' => $params['submit_user_id'],
            'submit_time' => $params['submit_time'],
            'priority' => $params['priority'],
            'ai_preview_result' => $params['ai_preview_result'],
            'audit_status' => $params['audit_status'],
            'content_id'=>$content_id,
            'description'=>$params['description'],
            'cover_url'=>$params['cover_url'],
            'submit_user_nickname'=>$params['submit_user_nickname'],
            'submit_user_auth_type'=>$params['submit_user_auth_type'],
            'pub_time'=>$params['pub_time'],
            'extra'=>$params['extra'],
            'platform'=>$params['platform'],
            'channel'=>$params['channel'],
            'tags'=>$params['tags'],
            'is_original'=>$params['is_original'],
            'created_at'=>date('Y-m-d H:i:s'),
            'updated_at'=>date('Y-m-d H:i:s')
        ]);
        // 添加到队列，异步处理（应对多任务提交场景）
        // 定时任务定期从队列中获取待入库任务，进行入库操作，任务提交过多的时候可以通过分布式方式进行任务分配处理，减少服务器压力
        $MEDIA_CONTENT_ADD = $GLOBALS['REDIS_KEYS']['MEDIA_CONTENT_ADD'];
        $redisObj = new ZDRedis();
        $redisObj->lpush($MEDIA_CONTENT_ADD,json_encode($params));
        

        $this->outputSuccess([
            'content_id' => $content_id
        ]);
    }

    /**
     * 获取单个媒体内容
     * GET /api/GetMedia/?content_id=MED-202308101523451234
     */
    public function actionGetMedia() {
        $contentId = arg('content_id',"");
        if (empty($contentId)) {
            $this->outputError(400, "缺少content_id参数");
        }
        $redisObj = new ZDRedis();
        $MEDIA_CONTENT_GET = $GLOBALS['REDIS_KEYS']['MEDIA_CONTENT_GET'];
        $cacheKey = $MEDIA_CONTENT_GET . $contentId;
        $data = $redisObj->get($cacheKey);
        if(isset($data) && !empty($data) ){
            $data = json_decode($data,true);
            $this->outputSuccess($data);
        }

        $media = new MediaContent();
        $data = $media->find(['content_id'=>$contentId]);

        if (!$data) {
            $this->outputError(404, "媒体内容不存在");
        }

        // 解析JSON字段
        $jsonFields = ['illegal_details', 'extra'];
        foreach ($jsonFields as $field) {
            if (!empty($data[$field])) {
                $data[$field] = json_decode($data[$field], true);
            }
        }
        // 缓存数据 10分钟过期
        $redisObj->set($cacheKey,json_encode($data),60*10); 
        $this->outputSuccess($data);
    }

    /**
     * 获取媒体内容列表
     * GET /api/ListMedia
     */
    public function actionListMedia() {
        $params = $this->getParams();
        $page = isset($params['page']) ? max(1, (int)$params['page']) : 1;
        $pageSize = isset($params['page_size']) ? min(100, (int)$params['page_size']) : 20;
        $offset = ($page - 1) * $pageSize;

        // 构建查询条件
        $where = [];
        $bind = [];
        $allowedFilters = [
            'content_type', 'audit_status', 'platform', 'category',
            'submit_user_id', 'is_original', 'ai_preview_result'
        ];
        foreach ($allowedFilters as $field) {
            if (isset($params[$field])) {
                $where[] = "`{$field}` = :{$field}";
                $bind[":{$field}"] = $params[$field];
            }
        }

        // 时间范围筛选
        if (!empty($params['start_submit_time'])) {
            $where[] = "submit_time >= :start_submit_time";
            $bind[':start_submit_time'] = $params['start_submit_time'];
        }
        if (!empty($params['end_submit_time'])) {
            $where[] = "submit_time <= :end_submit_time";
            $bind[':end_submit_time'] = $params['end_submit_time'];
        }

        $whereStr = $where ? "WHERE " . implode(' AND ', $where) : '';

        $media = new MediaContent();
        
        $total = $media->query("SELECT COUNT(*) as total FROM au_media_content {$whereStr}");
        $total = $total[0]["total"];

        // 列表查询
        $sql = "SELECT * FROM au_media_content {$whereStr} ORDER BY submit_time DESC LIMIT :offset, :page_size";
        $bind[':offset'] = $offset;
        $bind[':page_size'] = $pageSize;
        $list = $media->query($sql,$bind);

        // 解析JSON字段
        $jsonFields = ['illegal_details', 'extra'];
        foreach ($list as &$item) {
            foreach ($jsonFields as $field) {
                if (!empty($item[$field])) {
                    $item[$field] = json_decode($item[$field], true);
                }
            }
        }

        $this->outputSuccess([
            'list' => $list,
            'pagination' => [
                'page' => $page,
                'page_size' => $pageSize,
                'total' => $total,
                'pages' => ceil($total / $pageSize)
            ]
        ]);
    }

    /**
     * 更新媒体内容
     * POST /api/UpdateMedia?content_id=MED-202308101523451234
     */
    public function actionUpdateMedia() {
        $contentId = arg('content_id',"");
        if (empty($contentId)) {
            $this->outputError(400, "缺少content_id参数");
        }

        if (!$this->isContentIdExists($contentId)) {
            $this->outputError(404, "媒体内容不存在");
        }

        $params = $this->getParams();
        // 不允许更新的字段
        $forbidden = [
            'id', 'content_id', 'created_at', 'updated_at','submit_user_id','audit_status',
            'audit_user_id','audit_time','claimed_user_id','claimed_time','content_type',
            'ai_preview_result','submit_time','pub_time','title','content','extra','illegal_details',
            'ai_confidence','ai_suspect_type','ai_suspect_details','is_illegal','auditor_id','auditor_name',
            'claimed_by','chk_num'
        ];
        foreach ($forbidden as $field) {
            unset($params[$field]);
        }

        // 验证枚举值
        if (!empty($params)) {
            $this->validateEnums($params);
        }

        // 处理日期格式
        $dateFields = ['submit_time', 'pub_time', 'audit_time', 'claimed_time'];
        foreach ($dateFields as $field) {
            if (!empty($params[$field]) && !strtotime($params[$field])) {
                $this->outputError(400, "{$field}格式错误，需为datetime格式");
            }
        }

        // 处理JSON字段
        $jsonFields = ['illegal_details', 'extra'];
        foreach ($jsonFields as $field) {
            if (!empty($params[$field]) && !$this->isValidJson($params[$field])) {
                $this->outputError(400, "{$field}格式错误，需为JSON字符串");
            }
        }

        // 构建更新语句
        $set = [];
        $bind = [];
        foreach ($params as $k => $v) {
            if (in_array($k, $this->getTableColumns())) {
                $set[] = "`{$k}` = :{$k}";
                $bind[":{$k}"] = $v;
            }
        }

        if (empty($set)) {
            $this->outputError(400, "没有需要更新的字段");
        }

        $bind[':content_id'] = $contentId;
        $media = new MediaContent();
        $sql = "UPDATE au_media_content SET " . implode(',', $set) . " WHERE content_id = :content_id";
        
        $update = $media->query($sql,$bind);
        if($update == false){
            $this->outputError(500, "更新失败：");
        }
        

        $this->outputSuccess(['message' => '更新成功']);
    }

    /**
     * 删除媒体内容
     * DELETE /api/DeleteMedia?content_id=MED-202308101523451234
     */
    public function actionDeleteMedia() {
        $contentId = arg('content_id',"");
        if (empty($contentId)) {
            $this->outputError(400, "缺少content_id参数");
        }

        if (!$this->isContentIdExists($contentId)) {
            $this->outputError(404, "媒体内容不存在");
        }

        $db = $this->getDb();
        $stmt = $db->prepare("DELETE FROM au_media_content WHERE content_id = :content_id");
        if (!$stmt->execute([':content_id' => $contentId])) {
            $this->outputError(500, "删除失败：" . implode(',', $stmt->errorInfo()));
        }

        $this->outputSuccess(['message' => '删除成功']);
    }

    // 辅助方法
    private function getDb() {
        return new PDO("mysql:host=localhost;dbname=your_db;charset=utf8mb4", "username", "password");
    }
    /**
     * 获取请求参数
     */
    private function getParams() {
        $params = $_GET;
        if ($_SERVER['REQUEST_METHOD'] === 'POST' || $_SERVER['REQUEST_METHOD'] === 'PUT') {
            $postData = json_decode(file_get_contents('php://input'), true);
            $params = array_merge($params, $postData ?: []);
        }
        return $params;
    }
    /**
     * 查看内容记录是否存在
     */
    private function isContentIdExists($contentId) {
        $media = new MediaContent();
        $count = $media->findCount(['content_id'=>$contentId]);
        return $count>0 ? true : false;
    }
    /**
     * 获取媒体内容表字段
     */
    private function getTableColumns() {
        // 表字段列表
        return [
            'content_id', 'content_type', 'content_url', 'title', 'description',
            'category', 'cover_url', 'submit_user_id', 'submit_user_nickname',
            'submit_user_auth_type', 'submit_time', 'pub_time', 'priority',
            'platform', 'channel', 'tags', 'is_original', 'ai_preview_result',
            'ai_confidence', 'ai_suspect_type', 'ai_suspect_details', 'is_illegal',
            'illegal_details', 'audit_status', 'auditor_id', 'auditor_name',
            'audit_time', 'claimed_by', 'claimed_time', 'chk_num', 'extra','create_at','update_at'
        ];
    }
    /**
     * 验证枚举值
     */
    private function validateEnums($params) {
        $enums = [
            'content_type' => [1, 2, 3, 4],
            'submit_user_auth_type' => [0, 1, 2, 3],
            'priority' => [1, 2, 3],
            'is_original' => [0, 1, 2],
            'ai_preview_result' => [1, 2, 3],
            'is_illegal' => [1, 2],
            'audit_status' => [1, 2, 31, 32, 33, 34, 4]
        ];

        foreach ($enums as $field => $allowed) {
            if (isset($params[$field]) && !in_array($params[$field], $allowed)) {
                $this->outputError(400, "{$field}值无效，允许值：" . implode(',', $allowed));
            }
        }
    }
    /**
     * 验证JSON字符串
     */
    private function isValidJson($string) {
        json_decode($string);
        return json_last_error() === JSON_ERROR_NONE;
    }
    /**
     * 输出成功信息
     */
    private function outputSuccess($data) {
        $result = [
            'code' => 200,
            'msg' => 'success',
            'data' => $data
        ];
        echo json_encode($result, JSON_UNESCAPED_UNICODE);
        exit();
    }
    /**
     * 输出错误信息
     * @param int $code
     * @param string $msg
     */
    private function outputError($code, $msg) {
        $result = [
            'code' => $code,
            'msg' => $msg,
            'data' => []
        ];
        echo json_encode($result, JSON_UNESCAPED_UNICODE);
        exit();
    }


    /**
     * 基础文件上传
     * POST /api/UploadFile
     */
    public function actionUploadFile() {
        // 检查是否有文件上传
        if (empty($_FILES['file'])) {
            $this->outputError(400, "请选择要上传的文件");
        }

        $file = $_FILES['file'];
        $storageType = $this->getParams()['storage_type'] ?? 'local'; // local, aliyun, ksyun
        
        // 验证文件类型和大小
        $this->validateFile($file);
        
        try {
            // 根据存储类型选择上传适配器
            $uploader = $this->getUploadAdapter($storageType);
            $result = $uploader->upload($file);
            
            $this->outputSuccess([
                'file_url' => $result['url'],
                'file_key' => $result['key'],
                'storage_type' => $storageType
            ]);
        } catch (Exception $e) {
            $this->outputError(500, "上传失败：" . $e->getMessage());
        }
    }
    
    /**
     * 初始化分块上传
     * POST /api/InitiateMultipartUpload
     */
    public function actionInitiateMultipartUpload() {
        $params = $this->getParams();
        $required = ['file_name', 'file_size', 'mime_type'];
        foreach ($required as $field) {
            if (empty($params[$field])) {
                $this->outputError(400, "缺少必要参数：{$field}");
            }
        }
        
        $storageType = $params['storage_type'] ?? 'local';
        $uploadId = uniqid('upload_', true);
        $chunkSize = $params['chunk_size'] ?? 5 * 1024 * 1024; // 默认5MB分块
        $totalChunks = ceil($params[$required[1]] / $chunkSize);
        
        // 存储上传信息到Redis，有效期2小时
        $redis = new ZDRedis();
        $redis->set(
            "upload:{$uploadId}",
            json_encode([
                'file_name' => $params['file_name'],
                'file_size' => $params['file_size'],
                'mime_type' => $params['mime_type'],
                'storage_type' => $storageType,
                'chunk_size' => $chunkSize,
                'total_chunks' => $totalChunks,
                'created_at' => time(),
                'chunks_uploaded' => []
            ]),
            7200
        );
        
        // 如果是云存储，获取云服务的上传ID
        $uploader = $this->getUploadAdapter($storageType);
        $cloudUploadId = $uploader->initiateMultipartUpload($params['file_name'], $params['mime_type']);
        
        $this->outputSuccess([
            'upload_id' => $uploadId,
            'cloud_upload_id' => $cloudUploadId,
            'chunk_size' => $chunkSize,
            'total_chunks' => $totalChunks
        ]);
    }
    
    /**
     * 上传文件分块
     * POST /api/UploadChunk
     */
    public function actionUploadChunk() {
        $params = $this->getParams();
        $required = ['upload_id', 'chunk_number'];
        foreach ($required as $field) {
            if (empty($params[$field])) {
                $this->outputError(400, "缺少必要参数：{$field}");
            }
        }
        
        if (empty($_FILES['chunk'])) {
            $this->outputError(400, "请上传分块文件");
        }
        
        $redis = new ZDRedis();
        $uploadInfoKey = "upload:{$params['upload_id']}";
        $uploadInfo = $redis->get($uploadInfoKey);
        
        if (!$uploadInfo) {
            $this->outputError(400, "无效的upload_id");
        }
        
        $uploadInfo = json_decode($uploadInfo, true);
        $chunkNumber = (int)$params['chunk_number'];
        
        // 验证分块编号
        if ($chunkNumber < 1 || $chunkNumber > $uploadInfo['total_chunks']) {
            $this->outputError(400, "无效的分块编号");
        }
        
        try {
            $uploader = $this->getUploadAdapter($uploadInfo['storage_type']);
            $result = $uploader->uploadChunk(
                $params['upload_id'],
                $params['cloud_upload_id'] ?? '',
                $chunkNumber,
                $_FILES['chunk']
            );
            
            // 更新已上传分块信息
            $uploadInfo['chunks_uploaded'][] = $chunkNumber;
            $uploadInfo['chunks_uploaded'] = array_unique($uploadInfo['chunks_uploaded']);
            $redis->set($uploadInfoKey, json_encode($uploadInfo), 7200);
            
            $this->outputSuccess([
                'chunk_number' => $chunkNumber,
                'etag' => $result['etag'] ?? '',
                'progress' => count($uploadInfo['chunks_uploaded']) / $uploadInfo['total_chunks'] * 100
            ]);
        } catch (Exception $e) {
            $this->outputError(500, "分块上传失败：" . $e->getMessage());
        }
    }
    
    /**
     * 完成分块上传（合并分块）
     * POST /api/CompleteMultipartUpload
     */
    public function actionCompleteMultipartUpload() {
        $params = $this->getParams();
        if (empty($params['upload_id'])) {
            $this->outputError(400, "缺少upload_id参数");
        }
        
        $redis = new ZDRedis();
        $uploadInfoKey = "upload:{$params['upload_id']}";
        $uploadInfo = $redis->get($uploadInfoKey);
        
        if (!$uploadInfo) {
            $this->outputError(400, "无效的upload_id");
        }
        
        $uploadInfo = json_decode($uploadInfo, true);
        
        // 检查是否所有分块都已上传
        if (count($uploadInfo['chunks_uploaded']) != $uploadInfo['total_chunks']) {
            $this->outputError(400, "存在未上传的分块，请完成所有分块上传");
        }
        
        try {
            $uploader = $this->getUploadAdapter($uploadInfo['storage_type']);
            $result = $uploader->completeMultipartUpload(
                $params['upload_id'],
                $params['cloud_upload_id'] ?? '',
                $uploadInfo['file_name'],
                $uploadInfo['chunks_uploaded']
            );
            
            // 清理上传信息
            $redis->del($uploadInfoKey);
            
            $this->outputSuccess([
                'file_url' => $result['url'],
                'file_key' => $result['key'],
                'file_size' => $uploadInfo['file_size']
            ]);
        } catch (Exception $e) {
            $this->outputError(500, "合并分块失败：" . $e->getMessage());
        }
    }
    
    /**
     * 获取上传适配器
     */
    private function getUploadAdapter($storageType) {
        switch ($storageType) {
            case 'aliyun':
                return new AliyunOssUploader();
            case 'ksyun':
                return new KsyunKs3Uploader();
            case 'local':
            default:
                return new LocalUploader();
        }
    }
    
    /**
     * 验证文件合法性
     */
    private function validateFile($file) {
        // 检查上传错误
        if ($file['error'] != UPLOAD_ERR_OK) {
            $this->outputError(400, $this->getUploadErrorMsg($file['error']));
        }
        
        // 验证文件类型
        $allowedMimeTypes = [
            // 图片
            'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp',
            // 视频
            'video/mp4', 'video/mpeg', 'video/quicktime', 'video/x-msvideo', 'video/x-flv',
            // 音频
            'audio/mpeg', 'audio/wav', 'audio/mp3', 'audio/aac'
        ];
        
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->file($file['tmp_name']);
        
        if (!in_array($mimeType, $allowedMimeTypes)) {
            $this->outputError(400, "不支持的文件类型：{$mimeType}");
        }
        
        // 验证文件大小（最大100MB）
        $maxSize = 100 * 1024 * 1024;
        if ($file['size'] > $maxSize) {
            $this->outputError(400, "文件大小超过限制，最大支持100MB");
        }
    }
    
    /**
     * 获取上传错误信息
     */
    private function getUploadErrorMsg($errorCode) {
        $errors = [
            UPLOAD_ERR_INI_SIZE => '上传文件超过php.ini限制',
            UPLOAD_ERR_FORM_SIZE => '上传文件超过表单限制',
            UPLOAD_ERR_PARTIAL => '文件仅部分上传',
            UPLOAD_ERR_NO_FILE => '未上传文件',
            UPLOAD_ERR_NO_TMP_DIR => '缺少临时文件夹',
            UPLOAD_ERR_CANT_WRITE => '文件写入失败',
            UPLOAD_ERR_EXTENSION => '文件上传被扩展阻止'
        ];
        return $errors[$errorCode] ?? "上传错误，错误码：{$errorCode}";
    }


}

/**
 * 上传适配器接口
 */
interface UploadAdapter {
    public function upload($file);
    public function initiateMultipartUpload($fileName, $mimeType);
    public function uploadChunk($uploadId, $cloudUploadId, $chunkNumber, $file);
    public function completeMultipartUpload($uploadId, $cloudUploadId, $fileName, $chunks);
}

/**
 * 本地存储上传适配器
 */
class LocalUploader implements UploadAdapter {
    private $uploadDir;
    
    public function __construct() {
        $this->uploadDir = APP_DIR . '/uploads/';
        if (!is_dir($this->uploadDir)) {
            mkdir($this->uploadDir, 0755, true);
        }
    }
    
    public function upload($file) {
        $fileName = $this->generateFileName($file['name']);
        $targetPath = $this->uploadDir . $fileName;
        
        if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
            throw new Exception("文件保存失败");
        }
        
        return [
            'url' => '/uploads/' . $fileName,
            'key' => $fileName
        ];
    }
    
    public function initiateMultipartUpload($fileName, $mimeType) {
        $dir = $this->uploadDir . 'chunks/';
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
        return ''; // 本地存储不需要云服务的uploadId
    }
    
    public function uploadChunk($uploadId, $cloudUploadId, $chunkNumber, $file) {
        $chunkDir = $this->uploadDir . "chunks/{$uploadId}/";
        if (!is_dir($chunkDir)) {
            mkdir($chunkDir, 0755, true);
        }
        
        $targetPath = $chunkDir . "chunk_{$chunkNumber}";
        if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
            throw new Exception("分块保存失败");
        }
        
        return ['etag' => md5_file($targetPath)];
    }
    
    public function completeMultipartUpload($uploadId, $cloudUploadId, $fileName, $chunks) {
        $fileName = $this->generateFileName($fileName);
        $targetPath = $this->uploadDir . $fileName;
        $chunkDir = $this->uploadDir . "chunks/{$uploadId}/";
        
        // 合并分块
        $fp = fopen($targetPath, 'wb');
        foreach ($chunks as $chunkNumber) {
            $chunkPath = $chunkDir . "chunk_{$chunkNumber}";
            if (!file_exists($chunkPath)) {
                throw new Exception("分块文件缺失：{$chunkNumber}");
            }
            
            $chunkFp = fopen($chunkPath, 'rb');
            stream_copy_to_stream($chunkFp, $fp);
            fclose($chunkFp);
            unlink($chunkPath); // 删除临时分块
        }
        fclose($fp);
        rmdir($chunkDir); // 删除分块目录
        
        return [
            'url' => '/uploads/' . $fileName,
            'key' => $fileName
        ];
    }
    
    private function generateFileName($originalName) {
        $ext = pathinfo($originalName, PATHINFO_EXTENSION);
        return date('YmdHis') . '_' . uniqid() . ($ext ? ".{$ext}" : '');
    }
}

/**
 * 阿里云OSS上传适配器
 */
class AliyunOssUploader implements UploadAdapter {
    private $ossClient;
    private $bucket;
    
    public function __construct() {
        // 实际项目中建议从配置文件读取
        $this->bucket = 'your_bucket_name';
        
        $this->ossClient = new OssClient('LTAIvhEpFMVFtRlL', 'iInivWvLrIjsbcUG0AaqGry4W6Fg0G', 'http://oss-cn-hangzhou.aliyuncs.com');
    }
    
    public function upload($file) {
        $object = $this->generateObjectKey($file['name']);
        
        $res = $this->ossClient->uploadFile(
            $this->bucket,
            $object,
            $file['tmp_name']
        );
        
        return [
            'url' => $res['oss-request-url'],
            'key' => $object
        ];
    }
    
    public function initiateMultipartUpload($fileName, $mimeType) {
        $object = $this->generateObjectKey($fileName);
        
        $result = $this->ossClient->initiateMultipartUpload(
            $this->bucket,
            $object,
            ['Content-Type' => $mimeType]
        );
        
        return $result['UploadId'];
    }
    
    public function uploadChunk($uploadId, $cloudUploadId, $chunkNumber, $file) {
        $object = $this->generateObjectKeyFromUploadId($uploadId);
        
        $result = $this->ossClient->uploadPart(
            $this->bucket,
            $object,
            $cloudUploadId,
            $chunkNumber,
            file_get_contents($file['tmp_name'])
        );
        
        return ['etag' => $result['ETag']];
    }
    
    public function completeMultipartUpload($uploadId, $cloudUploadId, $fileName, $chunks) {
        $object = $this->generateObjectKey($fileName);
        
        // 准备分块列表
        $parts = [];
        foreach ($chunks as $chunkNumber) {
            $parts[] = [
                'PartNumber' => $chunkNumber,
                'ETag' => $this->getChunkEtag($uploadId, $chunkNumber)
            ];
        }
        
        $result = $this->ossClient->completeMultipartUpload(
            $this->bucket,
            $object,
            $cloudUploadId,
            $parts
        );
        
        return [
            'url' => $result['Location'],
            'key' => $object
        ];
    }
    
    private function generateObjectKey($originalName) {
        $ext = pathinfo($originalName, PATHINFO_EXTENSION);
        return 'uploads/' . date('Ymd/') . uniqid() . ($ext ? ".{$ext}" : '');
    }
    
    private function generateObjectKeyFromUploadId($uploadId) {
        // 实际实现中可以根据uploadId映射到对应的object key
        return 'uploads/temp/' . $uploadId;
    }
    
    private function getChunkEtag($uploadId, $chunkNumber) {
        // 从Redis获取分块的ETag
        $redis = new ZDRedis();
        $etag = $redis->get("upload:{$uploadId}:chunk:{$chunkNumber}:etag");
        return $etag;
    }
}

/**
 * 金山云KS3上传适配器
 */
class KsyunKs3Uploader implements UploadAdapter {
    private $ks3Client;
    private $bucket;
    private $region;
    
    public function __construct() {
        // 实际项目中建议从配置文件读取
        $accessKey = 'your_ksyun_access_key';
        $secretKey = 'your_ksyun_secret_key';
        $this->bucket = 'your_bucket_name';
        $this->region = 'beijing-6';
        $this->ks3Client = new Ks3Client("AKLTRy8w6Sg5TsiKlqptxF4Few","OFtAroQ8SOaSgtlHM6DlVaKNcZ9Psa2O0l0Rpr8Oj6XqTOww9XStw+PhXOjihLi5Kw==","ks3-cn-beijing.ksyun.com");
    }
    
    public function upload($file) {
        $object = $this->generateObjectKey($file['name']);
        
        $this->ks3Client->putObject([
            'Bucket' => $this->bucket,
            'Key' => $object,
            'SourceFile' => $file['tmp_name']
        ]);
        
        return [
            'url' => $this->getObjectUrl($object),
            'key' => $object
        ];
    }
    
    public function initiateMultipartUpload($fileName, $mimeType) {
        $object = $this->generateObjectKey($fileName);
        
        $result = $this->ks3Client->createMultipartUpload([
            'Bucket' => $this->bucket,
            'Key' => $object,
            'ContentType' => $mimeType
        ]);
        
        return $result['UploadId'];
    }
    
    public function uploadChunk($uploadId, $cloudUploadId, $chunkNumber, $file) {
        $object = $this->generateObjectKeyFromUploadId($uploadId);
        
        $result = $this->ks3Client->uploadPart([
            'Bucket' => $this->bucket,
            'Key' => $object,
            'UploadId' => $cloudUploadId,
            'PartNumber' => $chunkNumber,
            'SourceFile' => $file['tmp_name']
        ]);
        
        // 保存ETag
        $redis = new ZDRedis();
        $redis->set("upload:{$uploadId}:chunk:{$chunkNumber}:etag", $result['ETag'], 7200);
        
        return ['etag' => $result['ETag']];
    }
    
    public function completeMultipartUpload($uploadId, $cloudUploadId, $fileName, $chunks) {
        $object = $this->generateObjectKey($fileName);
        
        // 准备分块列表
        $parts = [];
        foreach ($chunks as $chunkNumber) {
            $redis = new ZDRedis();
            $etag = $redis->get("upload:{$uploadId}:chunk:{$chunkNumber}:etag");
            $parts[] = [
                'PartNumber' => $chunkNumber,
                'ETag' => $etag
            ];
        }
        
        $result = $this->ks3Client->completeMultipartUpload([
            'Bucket' => $this->bucket,
            'Key' => $object,
            'UploadId' => $cloudUploadId,
            'Parts' => $parts
        ]);
        
        return [
            'url' => $this->getObjectUrl($object),
            'key' => $object
        ];
    }
    
    private function generateObjectKey($originalName) {
        $ext = pathinfo($originalName, PATHINFO_EXTENSION);
        return 'uploads/' . date('Ymd/') . uniqid() . ($ext ? ".{$ext}" : '');
    }
    
    private function generateObjectKeyFromUploadId($uploadId) {
        return 'uploads/temp/' . $uploadId;
    }
    
    private function getObjectUrl($object) {
        return "https://{$this->bucket}.ks3-cn-{$this->region}.ksyuncs.com/{$object}";
    }
}